using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;

namespace BendyLines
{
    public class TransitionLine : IDisposable
    {
        static TransitionLine()
        {
            sTransitionPen = new Pen(Color.Green, 8.0f);
            sTransitionPen.EndCap = LineCap.ArrowAnchor;
            sTransitionPen.DashCap = DashCap.Triangle;
            sTransitionPen.StartCap = LineCap.RoundAnchor;
            sTransitionStretchedPen = new Pen(Color.FromArgb(20, Color.Green), 8.0f);
            sTransitionStretchedPen.EndCap = LineCap.ArrowAnchor;
            sTransitionStretchedPen.DashCap = DashCap.Triangle;
            sTransitionStretchedPen.StartCap = LineCap.RoundAnchor;
        }

        public TransitionLine(ScreenThumbnail pStartThumbnail, ScreenThumbnail pEndThumbnail)
        {
            this.mPath = new GraphicsPath();
            this.mStartThumbnail = pStartThumbnail;
            this.mEndThumbnail = pEndThumbnail;
            this.mStartThumbnail.LocationChanged += this.cScreenThumbnailHasMoved;
            this.mEndThumbnail.LocationChanged += this.cScreenThumbnailHasMoved;
            this.cScreenThumbnailHasMoved(null, EventArgs.Empty);
            this.mStretchedTooFar = false;
        }

        public Rectangle BoundingBox
        {
            get
            {
                return this.mBoundingBox;
            }
        }

        public ScreenThumbnail StartThumbnail
        {
            get
            {
                return this.mStartThumbnail;
            }
        }

        public ScreenThumbnail EndThumbnail
        {
            get
            {
                return this.mEndThumbnail;
            }
        }

        public bool StretchedTooFar
        {
            get
            {
                return this.mStretchedTooFar;
            }
        }

        public bool MouseOver
        {
            get
            {
                return this.mMouseOver;
            }
            set
            {
                this.mMouseOver = value;
            }
        }

        public bool Selected
        {
            get
            {
                return this.mSelected;
            }
        }

        public void Dispose()
        {
            // unsubscribe from the start and end thumbnails location changed events
            this.mStartThumbnail.LocationChanged -= this.cScreenThumbnailHasMoved;
            this.mEndThumbnail.LocationChanged -= this.cScreenThumbnailHasMoved;
        }

        public void Draw(Graphics pGraphics)
        {
            if (!this.mStretchedTooFar)
            {
                pGraphics.DrawCurve(sTransitionPen, this.mTransition, 0.5f);
                if (this.mMouseOver || this.mSelected)
                {
                    pGraphics.DrawEllipse(Pens.Red,
                        Rectangle.FromLTRB(this.mTransition[1].X - 6,
                            this.mTransition[1].Y - 6,
                            this.mTransition[1].X + 6,
                            this.mTransition[1].Y + 6));
                }
            }
            else
            {
                pGraphics.DrawCurve(sTransitionStretchedPen, this.mTransition, 0.5f);
            }
        }
        
        public void Select()
        {
            this.mSelected = true;
        }

        public void Deselect()
        {
            this.mSelected = false;
        }

        public void StrecthTo(Point pStrecthPosition)
        {
            this.mTransition[1].X = pStrecthPosition.X;
            this.mTransition[1].Y = pStrecthPosition.Y;

            double TOOFAR = 100;
            double distance = CalculateLineDistance(pStrecthPosition, this.mMiddlePoint);

            // check if we stretched too far
            this.mStretchedTooFar = (distance > TOOFAR);

            this.mPath.Reset();
            this.mPath.AddCurve(this.mTransition);
            RectangleF rf = this.mPath.GetBounds();
            this.mBoundingBox = new Rectangle((int)rf.X, (int)rf.Y, (int)rf.Width, (int)rf.Height);
            this.mBoundingBox.Inflate(10, 10);
        }

        public bool HitTest(Point pPoint)
        {
            return this.mPath.IsOutlineVisible(pPoint, sTransitionPen);
        }

        public bool ConnectedToThumbnail(ScreenThumbnail pThumbnail)
        {
            return (this.mEndThumbnail == pThumbnail) || (this.mStartThumbnail == pThumbnail);
        }

        public static void DrawTransition(Graphics pGraphics, Rectangle pStart, Rectangle pEnd)
        {
            pGraphics.DrawCurve(sTransitionPen, ComputeTransition(pStart, pEnd), 0.5f);
        }

        public static Point[] ComputeTransition(Rectangle pStartRect, Rectangle pEndRect)
        {
            Point start, end, middle;

            // initialize the start middle and end
            start = new Point();
            end = new Point();
            middle = new Point();

            int CURVE = 5;

            // given the following configuration:
            //  1  2  3
            //  4  5  6
            //  7  8  9
            // we will compute the curves between 5 and any other number 
            // where 5 is the starting rectangle
            if ((pEndRect.Right < pStartRect.Left) && (pEndRect.Bottom < pStartRect.Top))
            {
                // 1
                Console.WriteLine("1");
                start.X = pStartRect.Left;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Bottom;

                middle.X = ((end.X + start.X) / 2) - Math.Abs((end.X - start.X) / CURVE);
                middle.Y = ((end.Y + start.Y) / 2) + Math.Abs((end.Y - start.Y) / CURVE);
            }
            else if ((pEndRect.Left > pStartRect.Right) && (pEndRect.Bottom < pStartRect.Top))
            {
                // 3
                Console.WriteLine("3");
                start.X = pStartRect.Right;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Bottom;

                middle.X = ((end.X + start.X) / 2) + Math.Abs((end.X - start.X) / CURVE);
                middle.Y = ((end.Y + start.Y) / 2) + Math.Abs((end.Y - start.Y) / CURVE);
            }
            else if ((pEndRect.Right < pStartRect.Left) && (pEndRect.Top > pStartRect.Bottom))
            {
                // 7
                Console.WriteLine("7");
                start.X = pStartRect.Left;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Top;

                middle.X = ((end.X + start.X) / 2) - Math.Abs((end.X - start.X) / CURVE);
                middle.Y = ((end.Y + start.Y) / 2) - Math.Abs((end.Y - start.Y) / CURVE);
            }
            else if ((pEndRect.Left > pStartRect.Right) && (pEndRect.Top > pStartRect.Bottom))
            {
                // 9
                Console.WriteLine("9");
                start.X = pStartRect.Right;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Top;

                middle.X = ((end.X + start.X) / 2) + Math.Abs((end.X - start.X) / CURVE);
                middle.Y = ((end.Y + start.Y) / 2) - Math.Abs((end.Y - start.Y) / CURVE);
            }
            else if (pEndRect.Bottom < pStartRect.Top)
            {
                // 2
                Console.WriteLine("2");
                start.X = pStartRect.Left + pStartRect.Width / 2;
                start.Y = pStartRect.Top;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Bottom;

                middle.X = ((end.X + start.X) / 2);
                middle.Y = ((end.Y + start.Y) / 2);
            }
            else if (pEndRect.Top > pStartRect.Bottom)
            {
                // 8
                Console.WriteLine("8");
                start.X = pStartRect.Left + pStartRect.Width / 2;
                start.Y = pStartRect.Bottom;

                end.X = pEndRect.Left + pEndRect.Width / 2;
                end.Y = pEndRect.Top;

                middle.X = ((end.X + start.X) / 2);
                middle.Y = ((end.Y + start.Y) / 2);
            }
            else if (pEndRect.Right < pStartRect.Left)
            {
                // 4
                Console.WriteLine("4");
                start.X = pStartRect.Left;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Right;
                end.Y = pEndRect.Top + pEndRect.Height / 2;

                middle.X = ((end.X + start.X) / 2);
                middle.Y = ((end.Y + start.Y) / 2);
            }
            else //if (pEndRect.Left > pStartRect.Right)
            {
                // 6
                Console.WriteLine("6");
                start.X = pStartRect.Right;
                start.Y = pStartRect.Top + pStartRect.Height / 2;

                end.X = pEndRect.Left;
                end.Y = pEndRect.Top + pEndRect.Height / 2;

                middle.X = ((end.X + start.X) / 2);
                middle.Y = ((end.Y + start.Y) / 2);
            }

            // return the start middle and end points of the curve
            return new Point[] { start, middle, end };
        }

        public static double CalculateLineDistance(Point pInitialPoint, Point pFinalPoint)
        {
            int xDiff = pFinalPoint.X - pInitialPoint.X;
            int yDiff = pFinalPoint.Y - pInitialPoint.Y;
            return Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff));
        }

        private void cScreenThumbnailHasMoved(object pSender, EventArgs pEventArgs)
        {
            // recalculate the transition line
            this.mTransition = ComputeTransition(this.mStartThumbnail.Bounds, this.mEndThumbnail.Bounds);
            this.mMiddlePoint = new Point((this.mTransition[2].X + this.mTransition[0].X) / 2, 
                (this.mTransition[2].Y + this.mTransition[0].Y) / 2);
            this.mPath.Reset();
            this.mPath.AddCurve(this.mTransition);
            RectangleF rf = this.mPath.GetBounds();
            this.mBoundingBox = new Rectangle((int)rf.X, (int)rf.Y, (int)rf.Width, (int)rf.Height);
            this.mBoundingBox.Inflate(10, 10);
        }

        private ScreenThumbnail mStartThumbnail;
        private ScreenThumbnail mEndThumbnail;
        private GraphicsPath mPath;
        private Point[] mTransition;
        private Point mMiddlePoint;
        private Rectangle mBoundingBox;
        private bool mStretchedTooFar;
        private bool mMouseOver;
        private bool mSelected;

        private static Pen sTransitionPen;
        private static Pen sTransitionStretchedPen;
    }
}
